package com.linkgie.galaxyframework.metadata;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;

import com.linkgie.galaxyframework.utils.CollectionUtils;
import com.linkgie.galaxyframework.utils.TextUtils;
import com.linkgie.galaxyframework.utils.json.JSONUtils;

/**
 * 数据实体字段的内容类型；
 * 
 * @author huanghaiquan
 *
 */
public class ContentType {

	// public static final String ENUM_CONTENTTYPE_KEY_PREFIX = "enum.";

	/**
	 * 内容类型的公共参数；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface Params {
		public static final String LIMITS = "limits";

	}

	/**
	 * 文本；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface PlainText {

		public static final String TYPE = "plain";

		public static interface Params {
			public static final String MAX_LENGTH = "maxLength";
		}

	}

	/**
	 * 电话号码；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface TelNum {

		public static final String TYPE = "telnum";

		public static interface Params {
			/**
			 * 号码长度；
			 * 
			 * <p>
			 * 
			 * 整数，或整数列表；
			 * 
			 * <p>
			 * 
			 * 指定列表时表示可以接受多种长度；
			 */
			public static final String LENGTH = "length";
		}

	}

	/**
	 * 电子邮件；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface Email {

		public static final String TYPE = "email";

		public static interface Params {

			public static final String MAX_LENGTH = "maxLength";

			public static final String MIN_LENGTH = "minLength";

			/**
			 * 域名；
			 * <p>
			 * 如果指定了域名，则固定域名部分不必输入；
			 * <p>
			 * 如果未设置域名，则可以任意输入域名；
			 */
			public static final String DOMAIN = "domain";
		}

	}

	/**
	 * 公式；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface Formula {

		public static final String TYPE = "formula";

		public static interface Params {

			/**
			 * 字段标签是否是公式；
			 * 
			 * 默认为 false ；
			 */
			public static final String LABEL_FORMULA = "labelFormula";

			/**
			 * 字段值是否是公式；
			 * 
			 * 默认为 true ；
			 */
			public static final String VALUE_FORMULA = "valueFormula";
		}

	}

	/**
	 * 文本类型的枚举；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface Enum {

		public static final String TYPE = "enum";

		/**
		 * 枚举项的KEY参数名前缀；
		 */
		public static final String KEY_PARAM_PREFIX = "enum.";

		public static interface Params {
			/**
			 * 是否采用严格模式；
			 * <p>
			 * 可选值： true | false ；
			 * 
			 * <p>
			 * 严格模式下，只接受枚举范围的值；
			 */
			public static final String STRICT = "strict";
		}

	}

	/**
	 * 小数；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface Decimal {

		public static final String TYPE = "decimal";

		public static interface Params {
			public static final String MAX = "max";
			public static final String MIN = "min";
			public static final String PRECISION = "precision";
		}
	}

	/**
	 * 聚合；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface Aggregate {

		public static final String TYPE = "aggregate";

		public static interface Params {
			public static final String MODEL = "model";
			public static final String INTERFACE = "interface";
			public static final String CONDITION_INTERFACE = "conditionInterface";
			public static final String FOREIGN_KEY = "foreignKey";
			public static final String LABEL_KEY = "labelKey";
		}
	}

	/**
	 * 引用；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface Reference {

		public static final String TYPE = "ref";

		public static interface Params {
			public static final String MODEL = "model";
			public static final String INTERFACE = "interface";
			public static final String CONDITION_INTERFACE = "conditionInterface";
			public static final String VALUE_KEY = "valueKey";
			public static final String LABEL_KEY = "labelKey";
			public static final String CASCADE_KEY = "cascadeKey";
			@Deprecated
			public static final String CASCADE_SEPERATOR = "cascadeSeperator";
			@Deprecated
			public static final String REFRESH_OBJECTS = "refreshObjects";
			// public static final String DATA_SERVICE = "dataService";

		}
	}

	/**
	 * 组合；
	 * 
	 * @author huanghaiquan
	 *
	 */
	public static interface Composite {

		public static final String TYPE = "composite";

		public static interface Params {
			public static final String MODEL = "model";
			public static final String INTERFACE = "interface";
			public static final String CONDITION_INTERFACE = "conditionInterface";
			public static final String LABEL_KEY = "labelKey";
		}
	}

	private static final String TOKEN_REGEX = "[!#$%&'*+-.^_`|~A-Za-z0-9]+";
	private static final String QUOTED_STRING_REGEX = "([\"][^\"]*[\"])|(['][^']*['])";
	private static final String TYPE_REGEX = TOKEN_REGEX + "[ ]*";
	private static final String SUBTYPE_REGEX = "([/][ ]*" + TOKEN_REGEX + ")?";
	private static final String PARAM_NAME_REGEX = TOKEN_REGEX;

	private static final String PARAM_VALUE_TOKEN_REGEX = "((" + TOKEN_REGEX + ")|(" + QUOTED_STRING_REGEX + "))";
	private static final String PARAM_ARRAY_VALUE_REGEX = "([\\[][ ]*" + PARAM_VALUE_TOKEN_REGEX + "([ ]*[,][ ]*"
			+ PARAM_VALUE_TOKEN_REGEX + ")*[ ]*[\\]])";
	private static final String PARAM_VALUE_REGEX = "(" + PARAM_VALUE_TOKEN_REGEX + "|" + PARAM_ARRAY_VALUE_REGEX + ")";

	private static final String CONTENT_TYPE_REGEX = "^" + TYPE_REGEX + SUBTYPE_REGEX + "([ ]*[;][ ]*"
			+ PARAM_NAME_REGEX + "[ ]*[=][ ]*" + PARAM_VALUE_REGEX + "[ ]*)*$";
	// private static final String CONTENT_TYPE_REGEX = "^" + TOKEN_REGEX + "[
	// ]*([/][ ]*" + TOKEN_REGEX + ")?([ ]*[;][ ]*"
	// + TOKEN_REGEX + "[ ]*[=][ ]*((" + TOKEN_REGEX +
	// ")|([\"][^\"]*[\"])|(['][^']*[']))[ ]*)*$";
	// private static final String CONTENT_TYPE_REGEX =
	// "^[!#$%&'*+-.^_`|~A-Za-z0-9]+[ ]*([/][ ]*[!#$%&'*+-.^_`|~A-Za-z0-9]+)?([
	// ]*[;][ ]*[!#$%&'*+-.^_`|~A-Za-z0-9]+[ ]*[=][
	// ]*(([!#$%&'*+-.^_`|~A-Za-z0-9]+)|([\"][^\"]*[\"])|(['][^']*[']))[ ]*)*$";

	private static final String TYPE_SEPERATOR = "/";

	private static final String PARAMETER_SEPERATOR = ";";

	private static final String VALUE_SEPERATOR = "=";

	private String type;

	private String subtype;

	private LinkedHashMap<String, Object> params = new LinkedHashMap<>();

	public String getType() {
		return type;
	}

	public String getSubtype() {
		return subtype;
	}

	public boolean isEnum() {
		return Objects.equals(Enum.TYPE, type);
	}

	public String getFullType() {
		return subtype == null ? type : type + TYPE_SEPERATOR + subtype;
	}

	public ContentType(String type) {
		this(type, null);
	}

	public ContentType(String type, String subtype) {
		if (type == null) {
			throw new IllegalArgumentException("Type is null!");
		}
		type = type.trim();
		if (type.length() == 0) {
			throw new IllegalArgumentException("Type is null!");
		}
		this.type = type;

		if (!checkToken(type)) {
			throw new IllegalArgumentException("Syntax error for the type[" + type + "]!");
		}
		if (subtype != null) {
			subtype = subtype.trim();
			if (subtype.length() > 0) {
				if (!checkToken(subtype)) {
					throw new IllegalArgumentException("Syntax error for the type[" + subtype + "]!");
				}
				this.subtype = subtype;
			}
		}
	}

	/**
	 * 检查是否符合语法；
	 * 
	 * @param contentType
	 * @return
	 */
	public static boolean checkSyntax(String contentType) {
		return Pattern.matches(CONTENT_TYPE_REGEX, contentType);
	}

	private static boolean checkToken(String token) {
		return Pattern.matches(TOKEN_REGEX, token);
	}

	public boolean containsParameter(String paramName) {
		return this.params.containsKey(paramName);
	}

	public Object getParameter(String paramName) {
		return this.params.get(paramName);
	}

	public void setParamter(String paramName, Object value) {
		if (!checkToken(paramName)) {
			throw new IllegalArgumentException("Syntax error for the parameter name[" + paramName + "]!");
		}
		this.params.put(paramName, value);
	}

	private String encodeParameterValue(Object value) {
		return JSONUtils.toJSON(value);
	}

	/**
	 * 格式化的 ContentType 字符串；
	 */
	@Override
	public String toString() {
		StringBuilder str = new StringBuilder(this.type);
		if (this.subtype != null) {
			str.append(TYPE_SEPERATOR).append(this.subtype);
		}
		if (this.params.size() > 0) {
			for (Entry<String, Object> entry : this.params.entrySet()) {

				str.append(PARAMETER_SEPERATOR);
				str.append(entry.getKey());

				str.append(VALUE_SEPERATOR);
				Object value = entry.getValue();
				String encodedValue = encodeParameterValue(value);
				str.append(encodedValue);
			}
		}

		return str.toString();
	}

	public static ContentType resolve(String expression) {
		if (expression == null) {
			throw new IllegalArgumentException("Content type expression is null!");
		}
		if (!checkSyntax(expression)) {
			throw new IllegalArgumentException("Syntax error for the content type expression!");
		}
		expression = expression.trim();

		String type;
		String subtype = null;
		String parameterExp = null;
		int sepIdx = expression.indexOf(TYPE_SEPERATOR);
		if (sepIdx >= 0) {
			type = expression.substring(0, sepIdx);

			int paramIdx = expression.indexOf(PARAMETER_SEPERATOR);
			if (paramIdx >= 0) {
				subtype = expression.substring(sepIdx + 1, paramIdx);
				parameterExp = expression.substring(paramIdx + 1);
			} else {
				subtype = expression.substring(sepIdx + 1);
			}
		} else {
			int paramIdx = expression.indexOf(PARAMETER_SEPERATOR);
			if (paramIdx >= 0) {
				type = expression.substring(0, paramIdx);
				parameterExp = expression.substring(paramIdx + 1);
			} else {
				type = expression;
			}
		}

		ContentType contentType = new ContentType(type, subtype);

		// 解析参数；
		if (parameterExp != null) {
			parameterExp = parameterExp.trim();
			String[] paramSections = parameterExp.split(PARAMETER_SEPERATOR);
			for (String ps : paramSections) {
				String[] kv = ps.split(VALUE_SEPERATOR);
				String paramName = kv[0].trim();
				contentType.setParamter(paramName, JSONUtils.toValue(kv[1].trim()));
			}
		}

		return contentType;
	}

	public static String generateEnumContentType(Class<?> enumType) {
		if (!enumType.isEnum()) {
			throw new IllegalArgumentException("The specifield 'enumType' argument is not a enum type! ");
		}

		ContentTypeParameter[] enumParams = resolveEnumParameters(enumType);

		StringBuilder enumContentType = new StringBuilder(Enum.TYPE + ";");
		for (int i = 0; i < enumParams.length; i++) {
			if (i > 0) {
				enumContentType.append(";");
			}
			enumContentType.append(String.format("%s='%s'", enumParams[i].getKey(), enumParams[i].getValue()));
		}
		return enumContentType.toString();
	}

	public static ContentTypeParameter[] resolveEnumParameters(Class<?> enumType) {
		return resolveEnumParameters(enumType, null);
	}

	/**
	 * 解析枚举项；
	 * 
	 * @param enumType 枚举类型；
	 * @param excludes 要排除的枚举项列表；此参数的每一项对应枚举项的 key ；如果为 null ，则不排除任何枚举项；
	 * @return
	 */
	public static ContentTypeParameter[] resolveEnumParameters(Class<?> enumType, String[] excludes) {
		Map<String, EnumConstant> enumConstants = resolveEnumConstants(enumType);

		return convertToEnumParameters(enumConstants.values(), excludes);
	}

	/**
	 * 根据枚举常量列表，创建内容类型的枚举参数列表；
	 * 
	 * @param enumConstants 枚举常量列表；
	 * @param excludes      要排除的枚举项列表；此参数的每一项对应枚举项的 key ；如果为 null ，则不排除任何枚举项；
	 * @return
	 */
	public static ContentTypeParameter[] convertToEnumParameters(Collection<EnumConstant> enumConstants,
			String[] excludes) {
		// 根据参数，排除部分枚举项；
		Set<String> excludedKeys = CollectionUtils.merge(excludes);

		List<ContentTypeParameter> enumParams = new ArrayList<>(enumConstants.size());
		for (EnumConstant enumConstant : enumConstants) {
			if (excludedKeys.contains(enumConstant.getKey())) {
				// 排除枚举项；
				continue;
			}
			enumParams.add(new ContentTypeParameter(enumConstant));
		}

		return enumParams.toArray(new ContentTypeParameter[enumParams.size()]);
	}

	/**
	 * 解析枚举常量；
	 * 
	 * @param enumType
	 * @return
	 */
	public static Map<String, EnumConstant> resolveEnumConstants(Class<?> enumType) {
		Field keyField = null;
		Field labelField = null;

		final Object[] enumItems = enumType.getEnumConstants();
		if (enumItems.length == 0) {
			// 不接受空的枚举类型；
			throw new ModelMetadataException(
					"No enum item is declared in the enum type \"" + enumType.getName() + "\"!");
		}

		// 通过 EnumItem 声明的枚举项的名称；
		Map<Object, String> enumItemNames = new HashMap<>();

		Field[] fields = enumType.getFields();
		for (Field field : fields) {
			int modifiers = field.getModifiers();
			if (Modifier.isStatic(modifiers)) {
				EnumItem enumItemAnno = field.getAnnotation(EnumItem.class);
				if (enumItemAnno != null) {
					// 解析 DataEnum 声明的枚举项；
					String name = enumItemAnno.name();
					if (TextUtils.isBlank(name)) {
						name = enumItemAnno.value();
					}

					try {
						Object enumItem = field.get(null);
						if (TextUtils.isBlank(name)) {
							name = enumItem.toString();
						}
						enumItemNames.put(enumItem, name);
					} catch (IllegalArgumentException | IllegalAccessException e) {
						throw new IllegalStateException(e.getMessage(), e);
					}
				} else {
					// 忽略声明的枚举常量字段，只解析枚举常量值的内部字段；
					continue;
				}
			}
			DataKey dataKeyAnno = field.getAnnotation(DataKey.class);
			if (dataKeyAnno != null) {
				if (keyField != null) {
					throw new ModelMetadataException("More than one fields annotated as DataKey!");
				}
				keyField = field;
			}
			DataLabel dataLabelAnno = field.getAnnotation(DataLabel.class);
			if (dataLabelAnno != null) {
				if (labelField != null) {
					throw new ModelMetadataException("More than one fields annotated as DataLabel!");
				}
				labelField = field;
			}
		}

		Map<String, EnumConstant> enumConstants = new LinkedHashMap<>(enumItems.length);
		for (int i = 0; i < enumItems.length; i++) {
			Object enumItem = enumItems[i];
			String defaultName = enumItemNames.get(enumItem);
			EnumConstant enumConstant = resolveEnumConstant(keyField, labelField, enumItem, defaultName);
			EnumConstant confliction = enumConstants.putIfAbsent(enumConstant.getKey(), enumConstant);
			if (confliction != null) {
				throw new IllegalArgumentException("The enum constant ["+enumConstant.getKey()+"] conflict!");
			}
		}
		return enumConstants;
	}

	/**
	 * 解析生成枚举类型的内容参数；
	 * 
	 * @param keyField
	 * @param labelField
	 * @param enumConst
	 * @return
	 */
	private static EnumConstant resolveEnumConstant(Field keyField, Field labelField, Object enumConst,
			String defaultName) {
		String key;
		if (keyField == null) {
			key = enumConst.toString();
		} else {
			key = getEnumFieldStringValue(enumConst, keyField);
		}

		String name = defaultName == null ? key : defaultName;
		if (labelField != null) {
			name = getEnumFieldStringValue(enumConst, labelField);
		}
		return new EnumConstant(key, name);
	}

	/**
	 * 解析生成枚举类型的内容参数；
	 * 
	 * @param keyField
	 * @param labelField
	 * @param enumConst
	 * @return
	 */
	@SuppressWarnings("unused")
	private static ContentTypeParameter resolveEnumParameter(Field keyField, Field labelField, Object enumConst,
			String defaultName) {

		EnumConstant enumConstant = resolveEnumConstant(keyField, labelField, enumConst, defaultName);
		ContentTypeParameter param = new ContentTypeParameter(enumConstant);
		return param;
	}

	private static String getEnumFieldStringValue(Object enumConst, Field field) {
		try {
			Object value = field.get(enumConst);
			if (value == null) {
				return null;
			}
			return value.toString();
		} catch (IllegalArgumentException | IllegalAccessException e) {
			throw new ModelMetadataException(e.getMessage(), e);
		}
	}

	public static String formatEnumParamKey(String key) {
		return Enum.KEY_PARAM_PREFIX + key;
	}

	public static class EnumConstant {
		private String key;

		private String name;

		public EnumConstant(String key, String name) {
			this.key = key;
			this.name = name;
		}

		/**
		 * @return the key
		 */
		public String getKey() {
			return key;
		}

		/**
		 * @return the name
		 */
		public String getName() {
			return name;
		}
	}

	public static class ContentTypeParameter {

		private String key;

		private String value;

		public ContentTypeParameter(String key, String value) {
			this.key = key;
			this.value = value;
		}

		public ContentTypeParameter(EnumConstant enumConstant) {
			this.key = formatEnumParamKey(enumConstant.getKey());
			this.value = enumConstant.getName();
		}

		public String getKey() {
			return key;
		}

		public String getValue() {
			return value;
		}

	}

}
